/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.compiler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.feilong.lib.javassist.bytecode.Bytecode;
import com.feilong.lib.javassist.bytecode.Opcode;
import com.feilong.lib.javassist.compiler.ast.ASTList;
import com.feilong.lib.javassist.compiler.ast.ASTree;
import com.feilong.lib.javassist.compiler.ast.ArrayInit;
import com.feilong.lib.javassist.compiler.ast.AssignExpr;
import com.feilong.lib.javassist.compiler.ast.BinExpr;
import com.feilong.lib.javassist.compiler.ast.CallExpr;
import com.feilong.lib.javassist.compiler.ast.CastExpr;
import com.feilong.lib.javassist.compiler.ast.CondExpr;
import com.feilong.lib.javassist.compiler.ast.Declarator;
import com.feilong.lib.javassist.compiler.ast.DoubleConst;
import com.feilong.lib.javassist.compiler.ast.Expr;
import com.feilong.lib.javassist.compiler.ast.FieldDecl;
import com.feilong.lib.javassist.compiler.ast.InstanceOfExpr;
import com.feilong.lib.javassist.compiler.ast.IntConst;
import com.feilong.lib.javassist.compiler.ast.Keyword;
import com.feilong.lib.javassist.compiler.ast.Member;
import com.feilong.lib.javassist.compiler.ast.MethodDecl;
import com.feilong.lib.javassist.compiler.ast.NewExpr;
import com.feilong.lib.javassist.compiler.ast.Pair;
import com.feilong.lib.javassist.compiler.ast.Stmnt;
import com.feilong.lib.javassist.compiler.ast.StringL;
import com.feilong.lib.javassist.compiler.ast.Symbol;
import com.feilong.lib.javassist.compiler.ast.Variable;
import com.feilong.lib.javassist.compiler.ast.Visitor;

/* The code generator is implemented by three files:
 * CodeGen.java, MemberCodeGen.java, and JvstCodeGen.
 * I just wanted to split a big file into three smaller ones.
 */

public abstract class CodeGen extends Visitor implements Opcode,TokenId{

    static final String     javaLangObject    = "java.lang.Object";

    static final String     jvmJavaLangObject = "java/lang/Object";

    static final String     javaLangString    = "java.lang.String";

    static final String     jvmJavaLangString = "java/lang/String";

    protected Bytecode      bytecode;

    private int             tempVar;

    TypeChecker             typeChecker;

    /**
     * true if the last visited node is a return statement.
     */
    protected boolean       hasReturned;

    /**
     * Must be true if compilation is for a static method.
     */
    public boolean          inStaticMethod;

    protected List<Integer> breakList, continueList;

    /**
     * doit() in ReturnHook is called from atReturn().
     */
    protected static abstract class ReturnHook{

        ReturnHook next;

        /**
         * Returns true if the generated code ends with return,
         * throw, or goto.
         */
        protected abstract boolean doit(Bytecode b,int opcode);

        protected ReturnHook(CodeGen gen){
            next = gen.returnHooks;
            gen.returnHooks = this;
        }

        protected void remove(CodeGen gen){
            gen.returnHooks = next;
        }
    }

    protected ReturnHook returnHooks;

    /*
     * The following fields are used by atXXX() methods
     * for returning the type of the compiled expression.
     */
    protected int        exprType;   // VOID, NULL, CLASS, BOOLEAN, INT, ...

    protected int        arrayDim;

    protected String     className;  // JVM-internal representation

    public CodeGen(Bytecode b){
        bytecode = b;
        tempVar = -1;
        typeChecker = null;
        hasReturned = false;
        inStaticMethod = false;
        breakList = null;
        continueList = null;
        returnHooks = null;
    }

    public void setTypeChecker(TypeChecker checker){
        typeChecker = checker;
    }

    protected static void fatal() throws CompileError{
        throw new CompileError("fatal");
    }

    public static boolean is2word(int type,int dim){
        return dim == 0 && (type == DOUBLE || type == LONG);
    }

    public int getMaxLocals(){
        return bytecode.getMaxLocals();
    }

    public void setMaxLocals(int n){
        bytecode.setMaxLocals(n);
    }

    protected void incMaxLocals(int size){
        bytecode.incMaxLocals(size);
    }

    /**
     * Returns a local variable that single or double words can be
     * stored in.
     */
    protected int getTempVar(){
        if (tempVar < 0){
            tempVar = getMaxLocals();
            incMaxLocals(2);
        }

        return tempVar;
    }

    protected int getLocalVar(Declarator d){
        int v = d.getLocalVar();
        if (v < 0){
            v = getMaxLocals(); // delayed variable allocation.
            d.setLocalVar(v);
            incMaxLocals(1);
        }

        return v;
    }

    /**
     * Returns the JVM-internal representation of this class name.
     */
    protected abstract String getThisName();

    /**
     * Returns the JVM-internal representation of this super class name.
     */
    protected abstract String getSuperName() throws CompileError;

    /*
     * Converts a class name into a JVM-internal representation.
     *
     * It may also expand a simple class name to java.lang.*.
     * For example, this converts Object into java/lang/Object.
     */
    protected abstract String resolveClassName(ASTList name) throws CompileError;

    /*
     * Expands a simple class name to java.lang.*.
     * For example, this converts Object into java/lang/Object.
     */
    protected abstract String resolveClassName(String jvmClassName) throws CompileError;

    /**
     * @param name
     *            the JVM-internal representation.
     *            name is not exapnded to java.lang.*.
     */
    protected static String toJvmArrayName(String name,int dim){
        if (name == null){
            return null;
        }

        if (dim == 0){
            return name;
        }
        StringBuffer sbuf = new StringBuffer();
        int d = dim;
        while (d-- > 0){
            sbuf.append('[');
        }

        sbuf.append('L');
        sbuf.append(name);
        sbuf.append(';');

        return sbuf.toString();
    }

    protected static String toJvmTypeName(int type,int dim){
        char c = 'I';
        switch (type) {
            case BOOLEAN:
                c = 'Z';
                break;
            case BYTE:
                c = 'B';
                break;
            case CHAR:
                c = 'C';
                break;
            case SHORT:
                c = 'S';
                break;
            case INT:
                c = 'I';
                break;
            case LONG:
                c = 'J';
                break;
            case FLOAT:
                c = 'F';
                break;
            case DOUBLE:
                c = 'D';
                break;
            case VOID:
                c = 'V';
                break;
        }

        StringBuffer sbuf = new StringBuffer();
        while (dim-- > 0){
            sbuf.append('[');
        }

        sbuf.append(c);
        return sbuf.toString();
    }

    public void compileExpr(ASTree expr) throws CompileError{
        doTypeCheck(expr);
        expr.accept(this);
    }

    public boolean compileBooleanExpr(boolean branchIf,ASTree expr) throws CompileError{
        doTypeCheck(expr);
        return booleanExpr(branchIf, expr);
    }

    public void doTypeCheck(ASTree expr) throws CompileError{
        if (typeChecker != null){
            expr.accept(typeChecker);
        }
    }

    @Override
    public void atASTList(ASTList n) throws CompileError{
        fatal();
    }

    @Override
    public void atPair(Pair n) throws CompileError{
        fatal();
    }

    @Override
    public void atSymbol(Symbol n) throws CompileError{
        fatal();
    }

    @Override
    public void atFieldDecl(FieldDecl field) throws CompileError{
        field.getInit().accept(this);
    }

    @Override
    public void atMethodDecl(MethodDecl method) throws CompileError{
        ASTList mods = method.getModifiers();
        setMaxLocals(1);
        while (mods != null){
            Keyword k = (Keyword) mods.head();
            mods = mods.tail();
            if (k.get() == STATIC){
                setMaxLocals(0);
                inStaticMethod = true;
            }
        }

        ASTList params = method.getParams();
        while (params != null){
            atDeclarator((Declarator) params.head());
            params = params.tail();
        }

        Stmnt s = method.getBody();
        atMethodBody(s, method.isConstructor(), method.getReturn().getType() == VOID);
    }

    /**
     * @param isCons
     *            true if super() must be called.
     *            false if the method is a class initializer.
     */
    public void atMethodBody(Stmnt s,boolean isCons,boolean isVoid) throws CompileError{
        if (s == null){
            return;
        }

        if (isCons && needsSuperCall(s)){
            insertDefaultSuperCall();
        }

        hasReturned = false;
        s.accept(this);
        if (!hasReturned){
            if (isVoid){
                bytecode.addOpcode(Opcode.RETURN);
                hasReturned = true;
            }else{
                throw new CompileError("no return statement");
            }
        }
    }

    private boolean needsSuperCall(Stmnt body) throws CompileError{
        if (body.getOperator() == BLOCK){
            body = (Stmnt) body.head();
        }

        if (body != null && body.getOperator() == EXPR){
            ASTree expr = body.head();
            if (expr != null && expr instanceof Expr && ((Expr) expr).getOperator() == CALL){
                ASTree target = ((Expr) expr).head();
                if (target instanceof Keyword){
                    int token = ((Keyword) target).get();
                    return token != THIS && token != SUPER;
                }
            }
        }

        return true;
    }

    protected abstract void insertDefaultSuperCall() throws CompileError;

    @Override
    public void atStmnt(Stmnt st) throws CompileError{
        if (st == null){
            return; // empty
        }

        int op = st.getOperator();
        if (op == EXPR){
            ASTree expr = st.getLeft();
            doTypeCheck(expr);
            if (expr instanceof AssignExpr){
                atAssignExpr((AssignExpr) expr, false);
            }else if (isPlusPlusExpr(expr)){
                Expr e = (Expr) expr;
                atPlusPlus(e.getOperator(), e.oprand1(), e, false);
            }else{
                expr.accept(this);
                if (is2word(exprType, arrayDim)){
                    bytecode.addOpcode(POP2);
                }else if (exprType != VOID){
                    bytecode.addOpcode(POP);
                }
            }
        }else if (op == DECL || op == BLOCK){
            ASTList list = st;
            while (list != null){
                ASTree h = list.head();
                list = list.tail();
                if (h != null){
                    h.accept(this);
                }
            }
        }else if (op == IF){
            atIfStmnt(st);
        }else if (op == WHILE || op == DO){
            atWhileStmnt(st, op == WHILE);
        }else if (op == FOR){
            atForStmnt(st);
        }else if (op == BREAK || op == CONTINUE){
            atBreakStmnt(st, op == BREAK);
        }else if (op == TokenId.RETURN){
            atReturnStmnt(st);
        }else if (op == THROW){
            atThrowStmnt(st);
        }else if (op == TRY){
            atTryStmnt(st);
        }else if (op == SWITCH){
            atSwitchStmnt(st);
        }else if (op == SYNCHRONIZED){
            atSyncStmnt(st);
        }else{
            // LABEL, SWITCH label stament might be null?.
            hasReturned = false;
            throw new CompileError("sorry, not supported statement: TokenId " + op);
        }
    }

    private void atIfStmnt(Stmnt st) throws CompileError{
        ASTree expr = st.head();
        Stmnt thenp = (Stmnt) st.tail().head();
        Stmnt elsep = (Stmnt) st.tail().tail().head();
        if (compileBooleanExpr(false, expr)){
            hasReturned = false;
            if (elsep != null){
                elsep.accept(this);
            }

            return;
        }

        int pc = bytecode.currentPc();
        int pc2 = 0;
        bytecode.addIndex(0); // correct later

        hasReturned = false;
        if (thenp != null){
            thenp.accept(this);
        }

        boolean thenHasReturned = hasReturned;
        hasReturned = false;

        if (elsep != null && !thenHasReturned){
            bytecode.addOpcode(Opcode.GOTO);
            pc2 = bytecode.currentPc();
            bytecode.addIndex(0);
        }

        bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
        if (elsep != null){
            elsep.accept(this);
            if (!thenHasReturned){
                bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1);
            }

            hasReturned = thenHasReturned && hasReturned;
        }
    }

    private void atWhileStmnt(Stmnt st,boolean notDo) throws CompileError{
        List<Integer> prevBreakList = breakList;
        List<Integer> prevContList = continueList;
        breakList = new ArrayList<>();
        continueList = new ArrayList<>();

        ASTree expr = st.head();
        Stmnt body = (Stmnt) st.tail();

        int pc = 0;
        if (notDo){
            bytecode.addOpcode(Opcode.GOTO);
            pc = bytecode.currentPc();
            bytecode.addIndex(0);
        }

        int pc2 = bytecode.currentPc();
        if (body != null){
            body.accept(this);
        }

        int pc3 = bytecode.currentPc();
        if (notDo){
            bytecode.write16bit(pc, pc3 - pc + 1);
        }

        boolean alwaysBranch = compileBooleanExpr(true, expr);
        if (alwaysBranch){
            bytecode.addOpcode(Opcode.GOTO);
            alwaysBranch = breakList.size() == 0;
        }

        bytecode.addIndex(pc2 - bytecode.currentPc() + 1);
        patchGoto(breakList, bytecode.currentPc());
        patchGoto(continueList, pc3);
        continueList = prevContList;
        breakList = prevBreakList;
        hasReturned = alwaysBranch;
    }

    protected void patchGoto(List<Integer> list,int targetPc){
        for (int pc : list){
            bytecode.write16bit(pc, targetPc - pc + 1);
        }
    }

    private void atForStmnt(Stmnt st) throws CompileError{
        List<Integer> prevBreakList = breakList;
        List<Integer> prevContList = continueList;
        breakList = new ArrayList<>();
        continueList = new ArrayList<>();

        Stmnt init = (Stmnt) st.head();
        ASTList p = st.tail();
        ASTree expr = p.head();
        p = p.tail();
        Stmnt update = (Stmnt) p.head();
        Stmnt body = (Stmnt) p.tail();

        if (init != null){
            init.accept(this);
        }

        int pc = bytecode.currentPc();
        int pc2 = 0;
        if (expr != null){
            if (compileBooleanExpr(false, expr)){
                // in case of "for (...; false; ...)"
                continueList = prevContList;
                breakList = prevBreakList;
                hasReturned = false;
                return;
            }

            pc2 = bytecode.currentPc();
            bytecode.addIndex(0);
        }

        if (body != null){
            body.accept(this);
        }

        int pc3 = bytecode.currentPc();
        if (update != null){
            update.accept(this);
        }

        bytecode.addOpcode(Opcode.GOTO);
        bytecode.addIndex(pc - bytecode.currentPc() + 1);

        int pc4 = bytecode.currentPc();
        if (expr != null){
            bytecode.write16bit(pc2, pc4 - pc2 + 1);
        }

        patchGoto(breakList, pc4);
        patchGoto(continueList, pc3);
        continueList = prevContList;
        breakList = prevBreakList;
        hasReturned = false;
    }

    private void atSwitchStmnt(Stmnt st) throws CompileError{
        compileExpr(st.head());

        List<Integer> prevBreakList = breakList;
        breakList = new ArrayList<>();
        int opcodePc = bytecode.currentPc();
        bytecode.addOpcode(LOOKUPSWITCH);
        int npads = 3 - (opcodePc & 3);
        while (npads-- > 0){
            bytecode.add(0);
        }

        Stmnt body = (Stmnt) st.tail();
        int npairs = 0;
        for (ASTList list = body; list != null; list = list.tail()){
            if (((Stmnt) list.head()).getOperator() == CASE){
                ++npairs;
            }
        }

        // opcodePc2 is the position at which the default jump offset is.
        int opcodePc2 = bytecode.currentPc();
        bytecode.addGap(4);
        bytecode.add32bit(npairs);
        bytecode.addGap(npairs * 8);

        long[] pairs = new long[npairs];
        int ipairs = 0;
        int defaultPc = -1;
        for (ASTList list = body; list != null; list = list.tail()){
            Stmnt label = (Stmnt) list.head();
            int op = label.getOperator();
            if (op == DEFAULT){
                defaultPc = bytecode.currentPc();
            }else if (op != CASE){
                fatal();
            }else{
                pairs[ipairs++] = ((long) computeLabel(label.head()) << 32) + ((long) (bytecode.currentPc() - opcodePc) & 0xffffffff);
            }

            hasReturned = false;
            ((Stmnt) label.tail()).accept(this);
        }

        Arrays.sort(pairs);
        int pc = opcodePc2 + 8;
        for (int i = 0; i < npairs; ++i){
            bytecode.write32bit(pc, (int) (pairs[i] >>> 32));
            bytecode.write32bit(pc + 4, (int) pairs[i]);
            pc += 8;
        }

        if (defaultPc < 0 || breakList.size() > 0){
            hasReturned = false;
        }

        int endPc = bytecode.currentPc();
        if (defaultPc < 0){
            defaultPc = endPc;
        }

        bytecode.write32bit(opcodePc2, defaultPc - opcodePc);

        patchGoto(breakList, endPc);
        breakList = prevBreakList;
    }

    private int computeLabel(ASTree expr) throws CompileError{
        doTypeCheck(expr);
        expr = TypeChecker.stripPlusExpr(expr);
        if (expr instanceof IntConst){
            return (int) ((IntConst) expr).get();
        }
        throw new CompileError("bad case label");
    }

    private void atBreakStmnt(Stmnt st,boolean notCont) throws CompileError{
        if (st.head() != null){
            throw new CompileError("sorry, not support labeled break or continue");
        }

        bytecode.addOpcode(Opcode.GOTO);
        Integer pc = Integer.valueOf(bytecode.currentPc());
        bytecode.addIndex(0);
        if (notCont){
            breakList.add(pc);
        }else{
            continueList.add(pc);
        }
    }

    protected void atReturnStmnt(Stmnt st) throws CompileError{
        atReturnStmnt2(st.getLeft());
    }

    protected final void atReturnStmnt2(ASTree result) throws CompileError{
        int op;
        if (result == null){
            op = Opcode.RETURN;
        }else{
            compileExpr(result);
            if (arrayDim > 0){
                op = ARETURN;
            }else{
                int type = exprType;
                if (type == DOUBLE){
                    op = DRETURN;
                }else if (type == FLOAT){
                    op = FRETURN;
                }else if (type == LONG){
                    op = LRETURN;
                }else if (isRefType(type)){
                    op = ARETURN;
                }else{
                    op = IRETURN;
                }
            }
        }

        for (ReturnHook har = returnHooks; har != null; har = har.next){
            if (har.doit(bytecode, op)){
                hasReturned = true;
                return;
            }
        }

        bytecode.addOpcode(op);
        hasReturned = true;
    }

    private void atThrowStmnt(Stmnt st) throws CompileError{
        ASTree e = st.getLeft();
        compileExpr(e);
        if (exprType != CLASS || arrayDim > 0){
            throw new CompileError("bad throw statement");
        }

        bytecode.addOpcode(ATHROW);
        hasReturned = true;
    }

    /*
     * overridden in MemberCodeGen
     */
    protected void atTryStmnt(Stmnt st) throws CompileError{
        hasReturned = false;
    }

    private void atSyncStmnt(Stmnt st) throws CompileError{
        int nbreaks = getListSize(breakList);
        int ncontinues = getListSize(continueList);

        compileExpr(st.head());
        if (exprType != CLASS && arrayDim == 0){
            throw new CompileError("bad type expr for synchronized block");
        }

        Bytecode bc = bytecode;
        final int var = bc.getMaxLocals();
        bc.incMaxLocals(1);
        bc.addOpcode(DUP);
        bc.addAstore(var);
        bc.addOpcode(MONITORENTER);

        ReturnHook rh = new ReturnHook(this){

            @Override
            protected boolean doit(Bytecode b,int opcode){
                b.addAload(var);
                b.addOpcode(MONITOREXIT);
                return false;
            }
        };

        int pc = bc.currentPc();
        Stmnt body = (Stmnt) st.tail();
        if (body != null){
            body.accept(this);
        }

        int pc2 = bc.currentPc();
        int pc3 = 0;
        if (!hasReturned){
            rh.doit(bc, 0); // the 2nd arg is ignored.
            bc.addOpcode(Opcode.GOTO);
            pc3 = bc.currentPc();
            bc.addIndex(0);
        }

        if (pc < pc2){ // if the body is not empty
            int pc4 = bc.currentPc();
            rh.doit(bc, 0); // the 2nd arg is ignored.
            bc.addOpcode(ATHROW);
            bc.addExceptionHandler(pc, pc2, pc4, 0);
        }

        if (!hasReturned){
            bc.write16bit(pc3, bc.currentPc() - pc3 + 1);
        }

        rh.remove(this);

        if (getListSize(breakList) != nbreaks || getListSize(continueList) != ncontinues){
            throw new CompileError("sorry, cannot break/continue in synchronized block");
        }
    }

    private static int getListSize(List<Integer> list){
        return list == null ? 0 : list.size();
    }

    private static boolean isPlusPlusExpr(ASTree expr){
        if (expr instanceof Expr){
            int op = ((Expr) expr).getOperator();
            return op == PLUSPLUS || op == MINUSMINUS;
        }

        return false;
    }

    @Override
    public void atDeclarator(Declarator d) throws CompileError{
        d.setLocalVar(getMaxLocals());
        d.setClassName(resolveClassName(d.getClassName()));

        int size;
        if (is2word(d.getType(), d.getArrayDim())){
            size = 2;
        }else{
            size = 1;
        }

        incMaxLocals(size);

        /*
         * NOTE: Array initializers has not been supported.
         */
        ASTree init = d.getInitializer();
        if (init != null){
            doTypeCheck(init);
            atVariableAssign(null, '=', null, d, init, false);
        }
    }

    @Override
    public abstract void atNewExpr(NewExpr n) throws CompileError;

    @Override
    public abstract void atArrayInit(ArrayInit init) throws CompileError;

    @Override
    public void atAssignExpr(AssignExpr expr) throws CompileError{
        atAssignExpr(expr, true);
    }

    protected void atAssignExpr(AssignExpr expr,boolean doDup) throws CompileError{
        // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>=
        int op = expr.getOperator();
        ASTree left = expr.oprand1();
        ASTree right = expr.oprand2();
        if (left instanceof Variable){
            atVariableAssign(expr, op, (Variable) left, ((Variable) left).getDeclarator(), right, doDup);
        }else{
            if (left instanceof Expr){
                Expr e = (Expr) left;
                if (e.getOperator() == ARRAY){
                    atArrayAssign(expr, op, (Expr) left, right, doDup);
                    return;
                }
            }

            atFieldAssign(expr, op, left, right, doDup);
        }
    }

    protected static void badAssign(Expr expr) throws CompileError{
        String msg;
        if (expr == null){
            msg = "incompatible type for assignment";
        }else{
            msg = "incompatible type for " + expr.getName();
        }

        throw new CompileError(msg);
    }

    /*
     * op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=.
     *
     * expr and var can be null.
     */
    private void atVariableAssign(Expr expr,int op,Variable var,Declarator d,ASTree right,boolean doDup) throws CompileError{
        int varType = d.getType();
        int varArray = d.getArrayDim();
        String varClass = d.getClassName();
        int varNo = getLocalVar(d);

        if (op != '='){
            atVariable(var);
        }

        // expr is null if the caller is atDeclarator().
        if (expr == null && right instanceof ArrayInit){
            atArrayVariableAssign((ArrayInit) right, varType, varArray, varClass);
        }else{
            atAssignCore(expr, op, right, varType, varArray, varClass);
        }

        if (doDup){
            if (is2word(varType, varArray)){
                bytecode.addOpcode(DUP2);
            }else{
                bytecode.addOpcode(DUP);
            }
        }

        if (varArray > 0){
            bytecode.addAstore(varNo);
        }else if (varType == DOUBLE){
            bytecode.addDstore(varNo);
        }else if (varType == FLOAT){
            bytecode.addFstore(varNo);
        }else if (varType == LONG){
            bytecode.addLstore(varNo);
        }else if (isRefType(varType)){
            bytecode.addAstore(varNo);
        }else{
            bytecode.addIstore(varNo);
        }

        exprType = varType;
        arrayDim = varArray;
        className = varClass;
    }

    protected abstract void atArrayVariableAssign(ArrayInit init,int varType,int varArray,String varClass) throws CompileError;

    private void atArrayAssign(Expr expr,int op,Expr array,ASTree right,boolean doDup) throws CompileError{
        arrayAccess(array.oprand1(), array.oprand2());

        if (op != '='){
            bytecode.addOpcode(DUP2);
            bytecode.addOpcode(getArrayReadOp(exprType, arrayDim));
        }

        int aType = exprType;
        int aDim = arrayDim;
        String cname = className;

        atAssignCore(expr, op, right, aType, aDim, cname);

        if (doDup){
            if (is2word(aType, aDim)){
                bytecode.addOpcode(DUP2_X2);
            }else{
                bytecode.addOpcode(DUP_X2);
            }
        }

        bytecode.addOpcode(getArrayWriteOp(aType, aDim));
        exprType = aType;
        arrayDim = aDim;
        className = cname;
    }

    protected abstract void atFieldAssign(Expr expr,int op,ASTree left,ASTree right,boolean doDup) throws CompileError;

    protected void atAssignCore(Expr expr,int op,ASTree right,int type,int dim,String cname) throws CompileError{
        if (op == PLUS_E && dim == 0 && type == CLASS){
            atStringPlusEq(expr, type, dim, cname, right);
        }else{
            right.accept(this);
            if (invalidDim(exprType, arrayDim, className, type, dim, cname, false) || (op != '=' && dim > 0)){
                badAssign(expr);
            }

            if (op != '='){
                int token = assignOps[op - MOD_E];
                int k = lookupBinOp(token);
                if (k < 0){
                    fatal();
                }

                atArithBinExpr(expr, token, k, type);
            }
        }

        if (op != '=' || (dim == 0 && !isRefType(type))){
            atNumCastExpr(exprType, type);
        }

        // type check should be done here.
    }

    private void atStringPlusEq(Expr expr,int type,int dim,String cname,ASTree right) throws CompileError{
        if (!jvmJavaLangString.equals(cname)){
            badAssign(expr);
        }

        convToString(type, dim); // the value might be null.
        right.accept(this);
        convToString(exprType, arrayDim);
        bytecode.addInvokevirtual(javaLangString, "concat", "(Ljava/lang/String;)Ljava/lang/String;");
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
    }

    private boolean invalidDim(int srcType,int srcDim,String srcClass,int destType,int destDim,String destClass,boolean isCast){
        if (srcDim != destDim){
            if (srcType == NULL){
                return false;
            }else if (destDim == 0 && destType == CLASS && jvmJavaLangObject.equals(destClass)){
                return false;
            }else if (isCast && srcDim == 0 && srcType == CLASS && jvmJavaLangObject.equals(srcClass)){
                return false;
            }else{
                return true;
            }
        }

        return false;
    }

    @Override
    public void atCondExpr(CondExpr expr) throws CompileError{
        if (booleanExpr(false, expr.condExpr())){
            expr.elseExpr().accept(this);
        }else{
            int pc = bytecode.currentPc();
            bytecode.addIndex(0); // correct later
            expr.thenExpr().accept(this);
            int dim1 = arrayDim;
            bytecode.addOpcode(Opcode.GOTO);
            int pc2 = bytecode.currentPc();
            bytecode.addIndex(0);
            bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
            expr.elseExpr().accept(this);
            if (dim1 != arrayDim){
                throw new CompileError("type mismatch in ?:");
            }

            bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1);
        }
    }

    static final int[] binOp = {
                                 '+',
                                 DADD,
                                 FADD,
                                 LADD,
                                 IADD,
                                 '-',
                                 DSUB,
                                 FSUB,
                                 LSUB,
                                 ISUB,
                                 '*',
                                 DMUL,
                                 FMUL,
                                 LMUL,
                                 IMUL,
                                 '/',
                                 DDIV,
                                 FDIV,
                                 LDIV,
                                 IDIV,
                                 '%',
                                 DREM,
                                 FREM,
                                 LREM,
                                 IREM,
                                 '|',
                                 NOP,
                                 NOP,
                                 LOR,
                                 IOR,
                                 '^',
                                 NOP,
                                 NOP,
                                 LXOR,
                                 IXOR,
                                 '&',
                                 NOP,
                                 NOP,
                                 LAND,
                                 IAND,
                                 LSHIFT,
                                 NOP,
                                 NOP,
                                 LSHL,
                                 ISHL,
                                 RSHIFT,
                                 NOP,
                                 NOP,
                                 LSHR,
                                 ISHR,
                                 ARSHIFT,
                                 NOP,
                                 NOP,
                                 LUSHR,
                                 IUSHR };

    static int lookupBinOp(int token){
        int[] code = binOp;
        int s = code.length;
        for (int k = 0; k < s; k = k + 5){
            if (code[k] == token){
                return k;
            }
        }

        return -1;
    }

    @Override
    public void atBinExpr(BinExpr expr) throws CompileError{
        int token = expr.getOperator();

        /*
         * arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>>
         */
        int k = lookupBinOp(token);
        if (k >= 0){
            expr.oprand1().accept(this);
            ASTree right = expr.oprand2();
            if (right == null){
                return; // see TypeChecker.atBinExpr().
            }

            int type1 = exprType;
            int dim1 = arrayDim;
            String cname1 = className;
            right.accept(this);
            if (dim1 != arrayDim){
                throw new CompileError("incompatible array types");
            }

            if (token == '+' && dim1 == 0 && (type1 == CLASS || exprType == CLASS)){
                atStringConcatExpr(expr, type1, dim1, cname1);
            }else{
                atArithBinExpr(expr, token, k, type1);
            }
        }else{
            /*
             * equation: &&, ||, ==, !=, <=, >=, <, >
             */
            if (!booleanExpr(true, expr)){
                bytecode.addIndex(7);
                bytecode.addIconst(0); // false
                bytecode.addOpcode(Opcode.GOTO);
                bytecode.addIndex(4);
            }

            bytecode.addIconst(1); // true
        }
    }

    /*
     * arrayDim values of the two oprands must be equal.
     * If an oprand type is not a numeric type, this method
     * throws an exception.
     */
    private void atArithBinExpr(Expr expr,int token,int index,int type1) throws CompileError{
        if (arrayDim != 0){
            badTypes(expr);
        }

        int type2 = exprType;
        if (token == LSHIFT || token == RSHIFT || token == ARSHIFT){
            if (type2 == INT || type2 == SHORT || type2 == CHAR || type2 == BYTE){
                exprType = type1;
            }else{
                badTypes(expr);
            }
        }else{
            convertOprandTypes(type1, type2, expr);
        }

        int p = typePrecedence(exprType);
        if (p >= 0){
            int op = binOp[index + p + 1];
            if (op != NOP){
                if (p == P_INT && exprType != BOOLEAN){
                    exprType = INT; // type1 may be BYTE, ...
                }

                bytecode.addOpcode(op);
                return;
            }
        }

        badTypes(expr);
    }

    private void atStringConcatExpr(Expr expr,int type1,int dim1,String cname1) throws CompileError{
        int type2 = exprType;
        int dim2 = arrayDim;
        boolean type2Is2 = is2word(type2, dim2);
        boolean type2IsString = (type2 == CLASS && jvmJavaLangString.equals(className));

        if (type2Is2){
            convToString(type2, dim2);
        }

        if (is2word(type1, dim1)){
            bytecode.addOpcode(DUP_X2);
            bytecode.addOpcode(POP);
        }else{
            bytecode.addOpcode(SWAP);
        }

        // even if type1 is String, the left operand might be null.
        convToString(type1, dim1);
        bytecode.addOpcode(SWAP);

        if (!type2Is2 && !type2IsString){
            convToString(type2, dim2);
        }

        bytecode.addInvokevirtual(javaLangString, "concat", "(Ljava/lang/String;)Ljava/lang/String;");
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
    }

    private void convToString(int type,int dim) throws CompileError{
        final String method = "valueOf";

        if (isRefType(type) || dim > 0){
            bytecode.addInvokestatic(javaLangString, method, "(Ljava/lang/Object;)Ljava/lang/String;");
        }else if (type == DOUBLE){
            bytecode.addInvokestatic(javaLangString, method, "(D)Ljava/lang/String;");
        }else if (type == FLOAT){
            bytecode.addInvokestatic(javaLangString, method, "(F)Ljava/lang/String;");
        }else if (type == LONG){
            bytecode.addInvokestatic(javaLangString, method, "(J)Ljava/lang/String;");
        }else if (type == BOOLEAN){
            bytecode.addInvokestatic(javaLangString, method, "(Z)Ljava/lang/String;");
        }else if (type == CHAR){
            bytecode.addInvokestatic(javaLangString, method, "(C)Ljava/lang/String;");
        }else if (type == VOID){
            throw new CompileError("void type expression");
        }else{
            bytecode.addInvokestatic(javaLangString, method, "(I)Ljava/lang/String;");
        }
    }

    /*
     * Produces the opcode to branch if the condition is true.
     * The oprand (branch offset) is not produced.
     *
     * @return true if the compiled code is GOTO (always branch).
     * GOTO is not produced.
     */
    private boolean booleanExpr(boolean branchIf,ASTree expr) throws CompileError{
        boolean isAndAnd;
        int op = getCompOperator(expr);
        if (op == EQ){ // ==, !=, ...
            BinExpr bexpr = (BinExpr) expr;
            int type1 = compileOprands(bexpr);
            // here, arrayDim might represent the array dim. of the left oprand
            // if the right oprand is NULL.
            compareExpr(branchIf, bexpr.getOperator(), type1, bexpr);
        }else if (op == '!'){
            return booleanExpr(!branchIf, ((Expr) expr).oprand1());
        }else if ((isAndAnd = (op == ANDAND)) || op == OROR){
            BinExpr bexpr = (BinExpr) expr;
            if (booleanExpr(!isAndAnd, bexpr.oprand1())){
                exprType = BOOLEAN;
                arrayDim = 0;
                return true;
            }
            int pc = bytecode.currentPc();
            bytecode.addIndex(0); // correct later
            if (booleanExpr(isAndAnd, bexpr.oprand2())){
                bytecode.addOpcode(Opcode.GOTO);
            }

            bytecode.write16bit(pc, bytecode.currentPc() - pc + 3);
            if (branchIf != isAndAnd){
                bytecode.addIndex(6); // skip GOTO instruction
                bytecode.addOpcode(Opcode.GOTO);
            }
        }else if (isAlwaysBranch(expr, branchIf)){
            // Opcode.GOTO is not added here.  The caller must add it.
            exprType = BOOLEAN;
            arrayDim = 0;
            return true; // always branch
        }else{ // others
            expr.accept(this);
            if (exprType != BOOLEAN || arrayDim != 0){
                throw new CompileError("boolean expr is required");
            }

            bytecode.addOpcode(branchIf ? IFNE : IFEQ);
        }

        exprType = BOOLEAN;
        arrayDim = 0;
        return false;
    }

    private static boolean isAlwaysBranch(ASTree expr,boolean branchIf){
        if (expr instanceof Keyword){
            int t = ((Keyword) expr).get();
            return branchIf ? t == TRUE : t == FALSE;
        }

        return false;
    }

    static int getCompOperator(ASTree expr) throws CompileError{
        if (expr instanceof Expr){
            Expr bexpr = (Expr) expr;
            int token = bexpr.getOperator();
            if (token == '!'){
                return '!';
            }else if ((bexpr instanceof BinExpr) && token != OROR && token != ANDAND && token != '&' && token != '|'){
                return EQ; // ==, !=, ...
            }else{
                return token;
            }
        }

        return ' '; // others
    }

    private int compileOprands(BinExpr expr) throws CompileError{
        expr.oprand1().accept(this);
        int type1 = exprType;
        int dim1 = arrayDim;
        expr.oprand2().accept(this);
        if (dim1 != arrayDim){
            if (type1 != NULL && exprType != NULL){
                throw new CompileError("incompatible array types");
            }else if (exprType == NULL){
                arrayDim = dim1;
            }
        }

        if (type1 == NULL){
            return exprType;
        }
        return type1;
    }

    private static final int ifOp[]  = {
                                         EQ,
                                         IF_ICMPEQ,
                                         IF_ICMPNE,
                                         NEQ,
                                         IF_ICMPNE,
                                         IF_ICMPEQ,
                                         LE,
                                         IF_ICMPLE,
                                         IF_ICMPGT,
                                         GE,
                                         IF_ICMPGE,
                                         IF_ICMPLT,
                                         '<',
                                         IF_ICMPLT,
                                         IF_ICMPGE,
                                         '>',
                                         IF_ICMPGT,
                                         IF_ICMPLE };

    private static final int ifOp2[] = {
                                         EQ,
                                         IFEQ,
                                         IFNE,
                                         NEQ,
                                         IFNE,
                                         IFEQ,
                                         LE,
                                         IFLE,
                                         IFGT,
                                         GE,
                                         IFGE,
                                         IFLT,
                                         '<',
                                         IFLT,
                                         IFGE,
                                         '>',
                                         IFGT,
                                         IFLE };

    /*
     * Produces the opcode to branch if the condition is true.
     * The oprands are not produced.
     *
     * Parameter expr - compare expression ==, !=, <=, >=, <, >
     */
    private void compareExpr(boolean branchIf,int token,int type1,BinExpr expr) throws CompileError{
        if (arrayDim == 0){
            convertOprandTypes(type1, exprType, expr);
        }

        int p = typePrecedence(exprType);
        if (p == P_OTHER || arrayDim > 0){
            if (token == EQ){
                bytecode.addOpcode(branchIf ? IF_ACMPEQ : IF_ACMPNE);
            }else if (token == NEQ){
                bytecode.addOpcode(branchIf ? IF_ACMPNE : IF_ACMPEQ);
            }else{
                badTypes(expr);
            }
        }else if (p == P_INT){
            int op[] = ifOp;
            for (int i = 0; i < op.length; i += 3){
                if (op[i] == token){
                    bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]);
                    return;
                }
            }

            badTypes(expr);
        }else{
            if (p == P_DOUBLE){
                if (token == '<' || token == LE){
                    bytecode.addOpcode(DCMPG);
                }else{
                    bytecode.addOpcode(DCMPL);
                }
            }else if (p == P_FLOAT){
                if (token == '<' || token == LE){
                    bytecode.addOpcode(FCMPG);
                }else{
                    bytecode.addOpcode(FCMPL);
                }
            }else if (p == P_LONG){
                bytecode.addOpcode(LCMP); // 1: >, 0: =, -1: <
            }else{
                fatal();
            }

            int[] op = ifOp2;
            for (int i = 0; i < op.length; i += 3){
                if (op[i] == token){
                    bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]);
                    return;
                }
            }

            badTypes(expr);
        }
    }

    protected static void badTypes(Expr expr) throws CompileError{
        throw new CompileError("invalid types for " + expr.getName());
    }

    private static final int P_DOUBLE = 0;

    private static final int P_FLOAT  = 1;

    private static final int P_LONG   = 2;

    private static final int P_INT    = 3;

    private static final int P_OTHER  = -1;

    protected static boolean isRefType(int type){
        return type == CLASS || type == NULL;
    }

    private static int typePrecedence(int type){
        if (type == DOUBLE){
            return P_DOUBLE;
        }else if (type == FLOAT){
            return P_FLOAT;
        }else if (type == LONG){
            return P_LONG;
        }else if (isRefType(type)){
            return P_OTHER;
        }else if (type == VOID){
            return P_OTHER; // this is wrong, but ...
        }else{
            return P_INT; // BOOLEAN, BYTE, CHAR, SHORT, INT
        }
    }

    // used in TypeChecker.
    static boolean isP_INT(int type){
        return typePrecedence(type) == P_INT;
    }

    // used in TypeChecker.
    static boolean rightIsStrong(int type1,int type2){
        int type1_p = typePrecedence(type1);
        int type2_p = typePrecedence(type2);
        return type1_p >= 0 && type2_p >= 0 && type1_p > type2_p;
    }

    private static final int[] castOp = {
                                          /* D F L I */
                                          /* double */ NOP,
                                          D2F,
                                          D2L,
                                          D2I,
                                          /* float */ F2D,
                                          NOP,
                                          F2L,
                                          F2I,
                                          /* long */ L2D,
                                          L2F,
                                          NOP,
                                          L2I,
                                          /* other */ I2D,
                                          I2F,
                                          I2L,
                                          NOP };

    /*
     * do implicit type conversion.
     * arrayDim values of the two oprands must be zero.
     */
    private void convertOprandTypes(int type1,int type2,Expr expr) throws CompileError{
        boolean rightStrong;
        int type1_p = typePrecedence(type1);
        int type2_p = typePrecedence(type2);

        if (type2_p < 0 && type1_p < 0){
            return;
        }

        if (type2_p < 0 || type1_p < 0){
            badTypes(expr);
        }

        int op, result_type;
        if (type1_p <= type2_p){
            rightStrong = false;
            exprType = type1;
            op = castOp[type2_p * 4 + type1_p];
            result_type = type1_p;
        }else{
            rightStrong = true;
            op = castOp[type1_p * 4 + type2_p];
            result_type = type2_p;
        }

        if (rightStrong){
            if (result_type == P_DOUBLE || result_type == P_LONG){
                if (type1_p == P_DOUBLE || type1_p == P_LONG){
                    bytecode.addOpcode(DUP2_X2);
                }else{
                    bytecode.addOpcode(DUP2_X1);
                }

                bytecode.addOpcode(POP2);
                bytecode.addOpcode(op);
                bytecode.addOpcode(DUP2_X2);
                bytecode.addOpcode(POP2);
            }else if (result_type == P_FLOAT){
                if (type1_p == P_LONG){
                    bytecode.addOpcode(DUP_X2);
                    bytecode.addOpcode(POP);
                }else{
                    bytecode.addOpcode(SWAP);
                }

                bytecode.addOpcode(op);
                bytecode.addOpcode(SWAP);
            }else{
                fatal();
            }
        }else if (op != NOP){
            bytecode.addOpcode(op);
        }
    }

    @Override
    public void atCastExpr(CastExpr expr) throws CompileError{
        String cname = resolveClassName(expr.getClassName());
        String toClass = checkCastExpr(expr, cname);
        int srcType = exprType;
        exprType = expr.getType();
        arrayDim = expr.getArrayDim();
        className = cname;
        if (toClass == null){
            atNumCastExpr(srcType, exprType); // built-in type
        }else{
            bytecode.addCheckcast(toClass);
        }
    }

    @Override
    public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError{
        String cname = resolveClassName(expr.getClassName());
        String toClass = checkCastExpr(expr, cname);
        bytecode.addInstanceof(toClass);
        exprType = BOOLEAN;
        arrayDim = 0;
    }

    private String checkCastExpr(CastExpr expr,String name) throws CompileError{
        final String msg = "invalid cast";
        ASTree oprand = expr.getOprand();
        int dim = expr.getArrayDim();
        int type = expr.getType();
        oprand.accept(this);
        int srcType = exprType;
        int srcDim = arrayDim;
        if (invalidDim(srcType, arrayDim, className, type, dim, name, true) || srcType == VOID || type == VOID){
            throw new CompileError(msg);
        }

        if (type == CLASS){
            if (!isRefType(srcType) && srcDim == 0){
                throw new CompileError(msg);
            }

            return toJvmArrayName(name, dim);
        }else if (dim > 0){
            return toJvmTypeName(type, dim);
        }else{
            return null; // built-in type
        }
    }

    void atNumCastExpr(int srcType,int destType) throws CompileError{
        if (srcType == destType){
            return;
        }

        int op, op2;
        int stype = typePrecedence(srcType);
        int dtype = typePrecedence(destType);
        if (0 <= stype && stype < 3){
            op = castOp[stype * 4 + dtype];
        }else{
            op = NOP;
        }

        if (destType == DOUBLE){
            op2 = I2D;
        }else if (destType == FLOAT){
            op2 = I2F;
        }else if (destType == LONG){
            op2 = I2L;
        }else if (destType == SHORT){
            op2 = I2S;
        }else if (destType == CHAR){
            op2 = I2C;
        }else if (destType == BYTE){
            op2 = I2B;
        }else{
            op2 = NOP;
        }

        if (op != NOP){
            bytecode.addOpcode(op);
        }

        if (op == NOP || op == L2I || op == F2I || op == D2I){
            if (op2 != NOP){
                bytecode.addOpcode(op2);
            }
        }
    }

    @Override
    public void atExpr(Expr expr) throws CompileError{
        // array access, member access,
        // (unary) +, (unary) -, ++, --, !, ~

        int token = expr.getOperator();
        ASTree oprand = expr.oprand1();
        if (token == '.'){
            String member = ((Symbol) expr.oprand2()).get();
            if (member.equals("class")){
                atClassObject(expr); // .class
            }else{
                atFieldRead(expr);
            }
        }else if (token == MEMBER){ // field read
            /*
             * MEMBER ('#') is an extension by Javassist.
             * The compiler internally uses # for compiling .class
             * expressions such as "int.class".
             */
            atFieldRead(expr);
        }else if (token == ARRAY){
            atArrayRead(oprand, expr.oprand2());
        }else if (token == PLUSPLUS || token == MINUSMINUS){
            atPlusPlus(token, oprand, expr, true);
        }else if (token == '!'){
            if (!booleanExpr(false, expr)){
                bytecode.addIndex(7);
                bytecode.addIconst(1);
                bytecode.addOpcode(Opcode.GOTO);
                bytecode.addIndex(4);
            }

            bytecode.addIconst(0);
        }else if (token == CALL){
            fatal();
        }else{
            expr.oprand1().accept(this);
            int type = typePrecedence(exprType);
            if (arrayDim > 0){
                badType(expr);
            }

            if (token == '-'){
                if (type == P_DOUBLE){
                    bytecode.addOpcode(DNEG);
                }else if (type == P_FLOAT){
                    bytecode.addOpcode(FNEG);
                }else if (type == P_LONG){
                    bytecode.addOpcode(LNEG);
                }else if (type == P_INT){
                    bytecode.addOpcode(INEG);
                    exprType = INT; // type may be BYTE, ...
                }else{
                    badType(expr);
                }
            }else if (token == '~'){
                if (type == P_INT){
                    bytecode.addIconst(-1);
                    bytecode.addOpcode(IXOR);
                    exprType = INT; // type may be BYTE. ...
                }else if (type == P_LONG){
                    bytecode.addLconst(-1);
                    bytecode.addOpcode(LXOR);
                }else{
                    badType(expr);
                }

            }else if (token == '+'){
                if (type == P_OTHER){
                    badType(expr);
                }

                // do nothing. ignore.
            }else{
                fatal();
            }
        }
    }

    protected static void badType(Expr expr) throws CompileError{
        throw new CompileError("invalid type for " + expr.getName());
    }

    @Override
    public abstract void atCallExpr(CallExpr expr) throws CompileError;

    protected abstract void atFieldRead(ASTree expr) throws CompileError;

    public void atClassObject(Expr expr) throws CompileError{
        ASTree op1 = expr.oprand1();
        if (!(op1 instanceof Symbol)){
            throw new CompileError("fatal error: badly parsed .class expr");
        }

        String cname = ((Symbol) op1).get();
        if (cname.startsWith("[")){
            int i = cname.indexOf("[L");
            if (i >= 0){
                String name = cname.substring(i + 2, cname.length() - 1);
                String name2 = resolveClassName(name);
                if (!name.equals(name2)){
                    /*
                     * For example, to obtain String[].class,
                     * "[Ljava.lang.String;" (not "[Ljava/lang/String"!)
                     * must be passed to Class.forName().
                     */
                    name2 = MemberResolver.jvmToJavaName(name2);
                    StringBuffer sbuf = new StringBuffer();
                    while (i-- >= 0){
                        sbuf.append('[');
                    }

                    sbuf.append('L').append(name2).append(';');
                    cname = sbuf.toString();
                }
            }
        }else{
            cname = resolveClassName(MemberResolver.javaToJvmName(cname));
            cname = MemberResolver.jvmToJavaName(cname);
        }

        atClassObject2(cname);
        exprType = CLASS;
        arrayDim = 0;
        className = "java/lang/Class";
    }

    /*
     * MemberCodeGen overrides this method.
     */
    protected void atClassObject2(String cname) throws CompileError{
        int start = bytecode.currentPc();
        bytecode.addLdc(cname);
        bytecode.addInvokestatic("java.lang.Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
        int end = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int pc = bytecode.currentPc();
        bytecode.addIndex(0); // correct later

        bytecode.addExceptionHandler(start, end, bytecode.currentPc(), "java.lang.ClassNotFoundException");

        /*
         * -- the following code is for inlining a call to DotClass.fail().
         * 
         * int var = getMaxLocals();
         * incMaxLocals(1);
         * bytecode.growStack(1);
         * bytecode.addAstore(var);
         * 
         * bytecode.addNew("java.lang.NoClassDefFoundError");
         * bytecode.addOpcode(DUP);
         * bytecode.addAload(var);
         * bytecode.addInvokevirtual("java.lang.ClassNotFoundException",
         * "getMessage", "()Ljava/lang/String;");
         * bytecode.addInvokespecial("java.lang.NoClassDefFoundError", "<init>",
         * "(Ljava/lang/String;)V");
         */

        bytecode.growStack(1);
        bytecode.addInvokestatic(
                        com.feilong.lib.javassist.runtime.DotClass.class.getName(),
                        "fail",
                        "(Ljava/lang/ClassNotFoundException;)" + "Ljava/lang/NoClassDefFoundError;");
        bytecode.addOpcode(ATHROW);
        bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
    }

    public void atArrayRead(ASTree array,ASTree index) throws CompileError{
        arrayAccess(array, index);
        bytecode.addOpcode(getArrayReadOp(exprType, arrayDim));
    }

    protected void arrayAccess(ASTree array,ASTree index) throws CompileError{
        array.accept(this);
        int type = exprType;
        int dim = arrayDim;
        if (dim == 0){
            throw new CompileError("bad array access");
        }

        String cname = className;

        index.accept(this);
        if (typePrecedence(exprType) != P_INT || arrayDim > 0){
            throw new CompileError("bad array index");
        }

        exprType = type;
        arrayDim = dim - 1;
        className = cname;
    }

    protected static int getArrayReadOp(int type,int dim){
        if (dim > 0){
            return AALOAD;
        }

        switch (type) {
            case DOUBLE:
                return DALOAD;
            case FLOAT:
                return FALOAD;
            case LONG:
                return LALOAD;
            case INT:
                return IALOAD;
            case SHORT:
                return SALOAD;
            case CHAR:
                return CALOAD;
            case BYTE:
            case BOOLEAN:
                return BALOAD;
            default:
                return AALOAD;
        }
    }

    protected static int getArrayWriteOp(int type,int dim){
        if (dim > 0){
            return AASTORE;
        }

        switch (type) {
            case DOUBLE:
                return DASTORE;
            case FLOAT:
                return FASTORE;
            case LONG:
                return LASTORE;
            case INT:
                return IASTORE;
            case SHORT:
                return SASTORE;
            case CHAR:
                return CASTORE;
            case BYTE:
            case BOOLEAN:
                return BASTORE;
            default:
                return AASTORE;
        }
    }

    private void atPlusPlus(int token,ASTree oprand,Expr expr,boolean doDup) throws CompileError{
        boolean isPost = oprand == null; // ++i or i++?
        if (isPost){
            oprand = expr.oprand2();
        }

        if (oprand instanceof Variable){
            Declarator d = ((Variable) oprand).getDeclarator();
            int t = exprType = d.getType();
            arrayDim = d.getArrayDim();
            int var = getLocalVar(d);
            if (arrayDim > 0){
                badType(expr);
            }

            if (t == DOUBLE){
                bytecode.addDload(var);
                if (doDup && isPost){
                    bytecode.addOpcode(DUP2);
                }

                bytecode.addDconst(1.0);
                bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB);
                if (doDup && !isPost){
                    bytecode.addOpcode(DUP2);
                }

                bytecode.addDstore(var);
            }else if (t == LONG){
                bytecode.addLload(var);
                if (doDup && isPost){
                    bytecode.addOpcode(DUP2);
                }

                bytecode.addLconst(1);
                bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB);
                if (doDup && !isPost){
                    bytecode.addOpcode(DUP2);
                }

                bytecode.addLstore(var);
            }else if (t == FLOAT){
                bytecode.addFload(var);
                if (doDup && isPost){
                    bytecode.addOpcode(DUP);
                }

                bytecode.addFconst(1.0f);
                bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB);
                if (doDup && !isPost){
                    bytecode.addOpcode(DUP);
                }

                bytecode.addFstore(var);
            }else if (t == BYTE || t == CHAR || t == SHORT || t == INT){
                if (doDup && isPost){
                    bytecode.addIload(var);
                }

                int delta = token == PLUSPLUS ? 1 : -1;
                if (var > 0xff){
                    bytecode.addOpcode(WIDE);
                    bytecode.addOpcode(IINC);
                    bytecode.addIndex(var);
                    bytecode.addIndex(delta);
                }else{
                    bytecode.addOpcode(IINC);
                    bytecode.add(var);
                    bytecode.add(delta);
                }

                if (doDup && !isPost){
                    bytecode.addIload(var);
                }
            }else{
                badType(expr);
            }
        }else{
            if (oprand instanceof Expr){
                Expr e = (Expr) oprand;
                if (e.getOperator() == ARRAY){
                    atArrayPlusPlus(token, isPost, e, doDup);
                    return;
                }
            }

            atFieldPlusPlus(token, isPost, oprand, expr, doDup);
        }
    }

    public void atArrayPlusPlus(int token,boolean isPost,Expr expr,boolean doDup) throws CompileError{
        arrayAccess(expr.oprand1(), expr.oprand2());
        int t = exprType;
        int dim = arrayDim;
        if (dim > 0){
            badType(expr);
        }

        bytecode.addOpcode(DUP2);
        bytecode.addOpcode(getArrayReadOp(t, arrayDim));
        int dup_code = is2word(t, dim) ? DUP2_X2 : DUP_X2;
        atPlusPlusCore(dup_code, doDup, token, isPost, expr);
        bytecode.addOpcode(getArrayWriteOp(t, dim));
    }

    protected void atPlusPlusCore(int dup_code,boolean doDup,int token,boolean isPost,Expr expr) throws CompileError{
        int t = exprType;

        if (doDup && isPost){
            bytecode.addOpcode(dup_code);
        }

        if (t == INT || t == BYTE || t == CHAR || t == SHORT){
            bytecode.addIconst(1);
            bytecode.addOpcode(token == PLUSPLUS ? IADD : ISUB);
            exprType = INT;
        }else if (t == LONG){
            bytecode.addLconst(1);
            bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB);
        }else if (t == FLOAT){
            bytecode.addFconst(1.0f);
            bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB);
        }else if (t == DOUBLE){
            bytecode.addDconst(1.0);
            bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB);
        }else{
            badType(expr);
        }

        if (doDup && !isPost){
            bytecode.addOpcode(dup_code);
        }
    }

    protected abstract void atFieldPlusPlus(int token,boolean isPost,ASTree oprand,Expr expr,boolean doDup) throws CompileError;

    @Override
    public abstract void atMember(Member n) throws CompileError;

    @Override
    public void atVariable(Variable v) throws CompileError{
        Declarator d = v.getDeclarator();
        exprType = d.getType();
        arrayDim = d.getArrayDim();
        className = d.getClassName();
        int var = getLocalVar(d);

        if (arrayDim > 0){
            bytecode.addAload(var);
        }else{
            switch (exprType) {
                case CLASS:
                    bytecode.addAload(var);
                    break;
                case LONG:
                    bytecode.addLload(var);
                    break;
                case FLOAT:
                    bytecode.addFload(var);
                    break;
                case DOUBLE:
                    bytecode.addDload(var);
                    break;
                default: // BOOLEAN, BYTE, CHAR, SHORT, INT
                    bytecode.addIload(var);
                    break;
            }
        }
    }

    @Override
    public void atKeyword(Keyword k) throws CompileError{
        arrayDim = 0;
        int token = k.get();
        switch (token) {
            case TRUE:
                bytecode.addIconst(1);
                exprType = BOOLEAN;
                break;
            case FALSE:
                bytecode.addIconst(0);
                exprType = BOOLEAN;
                break;
            case NULL:
                bytecode.addOpcode(ACONST_NULL);
                exprType = NULL;
                break;
            case THIS:
            case SUPER:
                if (inStaticMethod){
                    throw new CompileError("not-available: " + (token == THIS ? "this" : "super"));
                }

                bytecode.addAload(0);
                exprType = CLASS;
                if (token == THIS){
                    className = getThisName();
                }else{
                    className = getSuperName();
                }
                break;
            default:
                fatal();
        }
    }

    @Override
    public void atStringL(StringL s) throws CompileError{
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
        bytecode.addLdc(s.get());
    }

    @Override
    public void atIntConst(IntConst i) throws CompileError{
        arrayDim = 0;
        long value = i.get();
        int type = i.getType();
        if (type == IntConstant || type == CharConstant){
            exprType = (type == IntConstant ? INT : CHAR);
            bytecode.addIconst((int) value);
        }else{
            exprType = LONG;
            bytecode.addLconst(value);
        }
    }

    @Override
    public void atDoubleConst(DoubleConst d) throws CompileError{
        arrayDim = 0;
        if (d.getType() == DoubleConstant){
            exprType = DOUBLE;
            bytecode.addDconst(d.get());
        }else{
            exprType = FLOAT;
            bytecode.addFconst((float) d.get());
        }
    }
}
